//---STD-------------------------------------------------------------
#include <sstream>
#include <iostream>
#include <fstream>

//---SShader----------------------------------------------------------
#include "CShader.h"

//---Using-----------------------------------------------------------
using namespace SShader;
using std::cerr;
using std::endl;

//-------------------------------------------------------------------

/**
  @brief NE DOIS PAS ÊTRE UTILISÉ
*/
CShader::CShader():
iVertexNStrings(0),
ppcVertexStrings(NULL),
piVertexLengths(NULL),
iFragmentNStrings(0),
ppcFragmentStrings(NULL),
piFragmentLengths(NULL)

{
  m_uiProgramShader = new unsigned int(0) ;
  m_uiVertexShader = new unsigned int(0) ;
  m_uiFragmentShader = new unsigned int(0) ;
  affiche_warning = false ;
}

/**
  @param _sVertex : Nom du fichier du Vertex shader (.vert)
  @param _sPixel : Nom du fichier du Fragment shader (.frag)
*/
CShader::CShader(string _sVertex, string _sPixel):
iVertexNStrings(0),
ppcVertexStrings(NULL),
piVertexLengths(NULL),
iFragmentNStrings(0),
ppcFragmentStrings(NULL),
piFragmentLengths(NULL)
{

  m_uiProgramShader = new unsigned int(0) ;
  m_uiVertexShader = new unsigned int(0) ;
  m_uiFragmentShader = new unsigned int(0) ;

  sVertex=_sVertex ;
  sPixel=_sPixel ;
  affiche_warning = false ;
}

/**
  @brief Destructeur
*/
CShader::~CShader() {

  delete m_uiProgramShader ;
  delete m_uiVertexShader ;
  delete m_uiFragmentShader ;

  if (ppcVertexStrings != NULL) {
    for (int i=0 ; i<iVertexNStrings ; i++) {
        delete[] ppcVertexStrings[i] ;
    }
    delete[] ppcVertexStrings  ;
    delete[] piFragmentLengths  ;
  }

  if (ppcFragmentStrings != NULL) {
    for (int i=0 ; i<iFragmentNStrings ; i++) {
        delete[] ppcFragmentStrings[i] ;
    }
    delete[] ppcFragmentStrings ;
    delete[] piFragmentLengths ;
  }
}

void CShader::deleteFragment  (unsigned int *&_uiId) {
  // Destruction des shaders
  glDeleteShader (*_uiId);

}

void CShader::deleteVertex (unsigned int *&_uiId) {
  // Destruction des shaders
  glDeleteShader (*_uiId) ;
}

void CShader::deleteProgram (unsigned int *&_uiId) {
  // Destruction du programme
  glDeleteProgram (*_uiId);
}

/**
  @brief constructeur par copie \n (copie indépendante sauf pour les pointeurs vers les données reste biensûr identique)
*/
CShader::CShader(const CShader &copie) {

  //Variable pour le shader
  affiche_warning =   copie.affiche_warning ;
  m_uiProgramShader = copie.m_uiProgramShader ;
  m_uiVertexShader = copie.m_uiVertexShader ;
  m_uiFragmentShader = copie.m_uiFragmentShader ;
  iVertexNStrings = copie.iVertexNStrings ;
  iFragmentNStrings = copie.iFragmentNStrings ;

  if (ppcVertexStrings != NULL) {
    piVertexLengths = new int [iVertexNStrings] ;
    ppcVertexStrings = new char* [iVertexNStrings] ;
    for (int i=0 ; i<iVertexNStrings ; i++) {
      piVertexLengths[i] =  copie.piVertexLengths[i] ;
      ppcVertexStrings[i] = new char [piVertexLengths[i]] ;
      for (int j=0 ; j<piVertexLengths[i] ; j++) {
        ppcVertexStrings[i][j] = copie.ppcVertexStrings[i][j] ;
      }
    }
  }

  if (ppcFragmentStrings != NULL) {
    piFragmentLengths = new int [iFragmentNStrings] ;
    ppcFragmentStrings = new char* [iFragmentNStrings] ;
    for (int i=0 ; i<iFragmentNStrings ; i++) {
      piFragmentLengths[i] =  copie.piFragmentLengths[i] ;
      ppcFragmentStrings[i] = new char [piFragmentLengths[i]] ;
      for (int j=0 ; j<piFragmentLengths[i] ; j++) {
        ppcFragmentStrings[i][j] = copie.ppcFragmentStrings[i][j] ;
      }
    }
  }
}

/**
 @brief Initialise le shader
 @return true si bonne exécution de l'initialisation, si Déjà initialisé return false
*/
bool CShader::initShader() {
  glewInitCShader() ;
  if (*m_uiProgramShader != 0) {
    cerr<<"Erreur : is init\n" ;
    return false ;
  }
  //creation d'un conteneur de shader
  *m_uiProgramShader = glCreateProgram() ;
//   cout<<"Program = "<<glCreateProgram()<<" \nProg= "<< *m_uiProgramShader<<endl ;
  //creation d'un vertex shader
  *m_uiVertexShader = glCreateShader(GL_VERTEX_SHADER) ;
  
  if (sVertex!="") {
    //envoie du vertex shader a OpenGL
    ShaderSourceFile (*m_uiVertexShader, iVertexNStrings, ppcVertexStrings, piVertexLengths, sVertex);

    //compilation du vertex shader
    glCompileShader(*m_uiVertexShader);
  
    //Test si la compilation s'est bien passer
    GLint compile_status = GL_TRUE;
    glGetShaderiv(*m_uiVertexShader, GL_COMPILE_STATUS, &compile_status);
    if(compile_status != GL_TRUE || affiche_warning)
    {
      cerr<<"---Vertex buffer---\n" ;
      int infoLogLength = 0;
      //recupère la taille du char* pour afficher l'erreur
      glGetProgramiv(*m_uiVertexShader, GL_INFO_LOG_LENGTH, &infoLogLength);
      //récupère l'erreur et l'affiche
      char *buffer = new char[infoLogLength];
      glGetShaderInfoLog(*m_uiVertexShader, infoLogLength, 0, buffer);
      cerr << buffer << endl ;
      delete[] buffer ;
    }
    //insertion du vertex shader dans le conteneur
    glAttachShader(*m_uiProgramShader, *m_uiVertexShader);
  }
  
  if (sPixel!="") {
    //creation d'un fragment shader
    *m_uiFragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
  
    //envoie du fragment shader a OpenGL
    ShaderSourceFile (*m_uiFragmentShader, iFragmentNStrings, ppcFragmentStrings, piFragmentLengths, sPixel);
  
    //compilation du fragment shader
    glCompileShader(*m_uiFragmentShader);
  
    //Test si la compilation s'est bien passer
    GLint compile_status = GL_TRUE;
    glGetShaderiv(*m_uiFragmentShader, GL_COMPILE_STATUS, &compile_status);
    if(compile_status != GL_TRUE || affiche_warning)
    {
      cerr<<"---Fragment buffer---\n" ;
      int infoLogLength = 0;
      //recupère la taille du char* pour afficher l'erreur
      glGetProgramiv(*m_uiFragmentShader, GL_INFO_LOG_LENGTH, &infoLogLength);
      //récupère l'erreur et l'affiche
      char *buffer = new char[infoLogLength];
      glGetShaderInfoLog(*m_uiFragmentShader, infoLogLength, 0, buffer);
      cerr << buffer << endl ;
      delete[] buffer ;
    }
    //insertion du fragment shader dans le conteneur
    glAttachShader(*m_uiProgramShader, *m_uiFragmentShader);
  }
  
  //finalization du shader dans OpenGL
  glLinkProgram(*m_uiProgramShader);
  
  //recuperation de resultat de la finalization
  GLint progLinkSuccess;
  glGetProgramiv(*m_uiProgramShader, GL_LINK_STATUS, &progLinkSuccess);
  if (!progLinkSuccess)
  {
    cerr<<"---Program buffer---\n" ;
    int infoLogLength = 0;
    //recupère la taille du char* pour afficher l'erreur
    glGetProgramiv(*m_uiProgramShader, GL_INFO_LOG_LENGTH, &infoLogLength);
    //récupère l'erreur et l'affiche
    char *buffer = new char[infoLogLength];
    glGetProgramInfoLog(*m_uiProgramShader, infoLogLength, 0, buffer);
    cerr << buffer << endl ;
    delete[] buffer ;
    return false ;
  }

  //test de validite du shader
  glValidateProgram(*m_uiProgramShader);

  //recuperation de resultat de la validation
  GLint validated;
  glGetProgramiv(*m_uiProgramShader,GL_VALIDATE_STATUS, &validated);
  if(!validated)
  {
        int infoLogLength = 0;
        //recupère la taille du char* pour afficher l'erreur
        glGetProgramiv(*m_uiProgramShader, GL_INFO_LOG_LENGTH, &infoLogLength);
        //récupère l'erreur et l'affiche
        char *buffer = new char [infoLogLength];
        glGetProgramInfoLog(*m_uiProgramShader, infoLogLength, 0, buffer);

        cerr << buffer << endl ;
        delete[] buffer ;
        return false ;
  }
  return true ;
}

/**
  @brief Chargement de fichier Linux / Windows
  @param filename : Nom du fichier à charger
  @return contenue du fichier
*/
string CShader::LoadFichier (string filename) {
//----------------First Version----------------
//   string retour, tmp ;
//   char tmp_char = ' '  ;
//   ifstream file (filename.c_str(), std::ios::binary);
// 
// 
//   while ( !file.eof() )
//   {
//     retour += tmp_char;
//     file.get( tmp_char ) ;
//   }
//   return retour ;
//----------------Second Version----------------
  string retour ;
  std::ifstream file (filename.c_str());

  if (file) {
    std::stringstream buffer ;
    buffer << file.rdbuf();
    file.close();
    
    retour = buffer.str() ;
    retour.push_back ('\0');
  } else {
    cerr<<"Erreur CShader : No Find File\n" ;
  }
  return retour ;
}

/**
  @brief Chargement du code source du shader
  @param shaderObj : Id du shader
  @param nstrings  : nombre de shader à charger
  @param strings   : code source shader sinon NULL
  @param lengths   : taille de strings
  @param filename  : Nom du fichier shader sinon NULL
*/
bool CShader::ShaderSourceFile(unsigned int shaderObj, GLsizei &nstrings,
                                  char ** & strings, int * &lengths,
                                  string filename) {

  if (strings == NULL) {
    string progtexte = LoadFichier ( filename ) ;
    const char* texte = progtexte.c_str() ;
    glShaderSource(shaderObj, 1, const_cast<const char **>(&texte), NULL) ;
  }else {
    glShaderSource(shaderObj, nstrings, const_cast<const GLcharARB **>(strings), lengths) ;
  }
  return true ;
}

bool CShader::TestExtention () {

    if (!GL_ARB_vertex_array_object ||
        !GL_ARB_vertex_buffer_object ||
        !GL_ARB_vertex_program ||
        !GL_ARB_vertex_shader ||
        !GL_ARB_geometry_shader4 ||
        !GL_ARB_fragment_program ||
        !GL_ARB_fragment_shader ||
        !GL_ARB_fragment_program_shadow ||
        !GL_ARB_multitexture ||
        !GL_ARB_shader_objects ||
        !GL_ARB_shading_language_100 ) {
        std::cout<<"OpenGL Shading Language extensions not available\n"<<endl ;
        return false ;
    }
    std::cout<<"OpenGL Shading Language extensions Ok\n"<<endl ;
    return true ;
}

bool CShader::changeWarning () {
  affiche_warning = !affiche_warning ;
  return affiche_warning ;
}

void CShader::glewInitCShader() {
  #ifdef __glew_h__
  static GLenum err = GLEW_ERROR_NO_GL_VERSION ;
 if (GLEW_ERROR_NO_GL_VERSION == err) {
    err = glewInit() ;
  }
  if (GLEW_OK != err)
  {
    /* Problem: glewInit failed, something is seriously wrong. */
    std::cerr<<"Error Init Glew : "<<glewGetErrorString(err)<<std::endl ;
  }
//   else {
//     std::cerr<<"Init Glew : "<<glewGetErrorString(err)<<std::endl ;
//   }
  #endif

}
